LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

semantic_segmentation

2022/4/10

语义分割

训练过自己数据集的语义分割模型

但是都是用的别人现成的代码

这一次试一下从头搭一下


数据集制作

之前有自己标过目标检测的数据集,用的工具是labelImg,挺好用的

这一次标语义分割的数据集,用的labelme

在一个目录下,创建imgs文件夹存放原始图片,创建jsons文件夹存放输出

然后在该目录下执行

labelme imgs --output jsons --nodata --autosave

简单来讲就是标注imgs的图片输出到jsons中 然后jsons的输出没有包含imgs的数据 自动保存

具体含义可以通过labelme -h获得

使用ctrl+N就可以开始标注了

如果标注位置相似,例如连续视频帧

可以用ctrl+shift+D,将当前标注的复制到下一张,然后微调即可


标完之后我们只有jsons文件

我们需要通过jsons文件获得marks

labelme自带了labelme_json_to_dataset

可以通过

labelme_json_to_dataset 00000.json --out marks

将00000.json对应的图片转成dataset到marks

生成的文件包括原图片、红色marks、原图和红色marks融合、label.txt文件

但是他有个缺点,就是只能针对一个文件,而且我需要的是二值化白色marks

他的实现也很简单,存放在labelme/cli/json_to_dataset.py中

我根据自己的需求将他的代码修改如下

import argparse
import base64
import json
import os
import os.path as osp
from labelme import utils

import numpy as np
import cv2 as cv


def main():
    # python json_to_dataset.py jsons --out masks
    # 命令行参数解析
    parser = argparse.ArgumentParser()
    parser.add_argument("json_file")
    parser.add_argument("-o", "--out", default=None)
    args = parser.parse_args()

    json_dir = args.json_file  # json存放目录
    file_list = os.listdir(json_dir)  # 所有json文件
    for file in file_list:
        index = os.path.splitext(file)[0]  # 文件名 用于后续保存
        json_file = os.path.join(json_dir, file)  # json路径

        # 判断目录是否存在
        out_dir = args.out
        if not osp.exists(args.out):
            os.mkdir(args.out)

        # 获取img
        data = json.load(open(json_file))
        image_path = os.path.join(os.path.dirname(json_file), data["imagePath"])
        with open(image_path, "rb") as f:
            image_data = f.read()
            image_data = base64.b64encode(image_data).decode("utf-8")
        img = utils.img_b64_to_arr(image_data)

        # 获取label
        label_name_to_value = {"_background_": 0, 'line': 1}
        lbl, _ = utils.shapes_to_label(
            img.shape, data["shapes"], label_name_to_value
        ) #不同版本可能这里返回值要写成 lbl= 而不是 lbl, _ =

        # 转为二值化图像并保存
        label = np.array(lbl)
        label[np.where(label != 0)] = 255
        save_path = os.path.join(out_dir, index + ".png")
        cv.imwrite(save_path, label)

        print(json_file, '  ->  ', save_path)  # 打印查看进度


if __name__ == "__main__":
    main()

基本是用他的思路 删去不需要的内容 最终读写速度也是比较快的

然后就可以直接对整个文件夹的jsons提取黑白mask到masks文件夹

python json_to_dataset.py jsons --out masks


接下来记录一些比较需要注意的点

图像预处理

用pytorch做计算机视觉经常会接触到Numpy和PIL图像转换,实现如下:

img =Image.fromarray(img) # numpy转PIL
img =np.array(img) # PIL转numpy

但是使用时要注意,Image库读入的时RGB图像,Opencv库读入的时BGR图像


其实仔细一想,numpy可以转为tensor,opencv读入的也是numpy格式

用numpy处理矩阵数据,转换成tensor训练,本来是不需要经过PIL图像的

但是因为预处理常常会用到torchvision里的transfroms,这里的输入是PIL图像

为了避免类型频繁转换,我手动实现了预处理,这样子全过程都不涉及PIL图像


例如下列这个transforms就可以改成

img_transforms = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(), 
])

就可以改成

def normalized(data):
    data_min = np.min(data)
    data_max = np.max(data)

    if data_max - data_min != 0:
        return (data - data_min) / (data_max - data_min)
    else:
        if data_max != 0:
            return data / data_max
        else:
            return data
        
img = np.transpose(cv.resize(img, (256, 256)), (2, 0, 1))  # resize 和 通道交换
img_tensor = torch.Tensor(normalized(img))

transpose的用法 :原本是 (w,h,c)对应(0,1,2),需要转成(c,w,h)所以是(2,0,1)



Opencv的索引

因为经常会用到 但是又很容易忘记,记录一下

height, width = output.shape[:2]  # shape前两个元素是高和宽
img[row][col]  # 图像索引是行和列
cv.circle(show_img, (col, row), 3, (0, 0, 255))  # circle 是列和行